package com.explorex.infra.puma.gateway.netty.ws.message.handler;

import com.alibaba.fastjson.JSON;
import com.explorex.infra.puma.gateway.netty.common.*;
import com.explorex.infra.puma.gateway.netty.common.enums.LonglinkErrorEnum;
import com.explorex.infra.puma.gateway.netty.common.utils.JacksonObjectMapper;
import com.explorex.infra.puma.gateway.netty.ws.WebSocketSession;
import com.explorex.infra.puma.gateway.netty.ws.consts.ChannelConst;
import com.explorex.infra.puma.gateway.netty.ws.enums.MessageTypeEnum;
import com.explorex.infra.puma.gateway.netty.ws.handler.HttpServerHandler;
import com.explorex.infra.puma.gateway.netty.ws.message.Message;
import com.explorex.infra.puma.gateway.netty.ws.message.MessageBody;
import com.explorex.infra.puma.gateway.netty.ws.message.MessageHeader;
import com.explorex.infra.puma.gateway.netty.ws.message.RespMessageBody;
import com.explorex.infra.puma.gateway.netty.ws.model.AuthResponse;
import com.google.common.base.Preconditions;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


/**
 * @author: JacceYang
 * @createTime: 2022/07/15 1:15 下午
 * @description: 消息鉴权处理类
 */
@Slf4j
public class AuthMessageProcessor extends AbstractBaseProcessor {


    private static final ExecutorService threadPoolExecutor = Executors.newCachedThreadPool();


    @Override
    public short getMessageType() {
        return MessageTypeEnum.AUTH.getType();
    }

    @Override
    public Message process(WebSocketSession session, Message messageFrame) {
        long start = System.currentTimeMillis();
        if (session.getAuth() == AuthStatusEnum.AUTHED) {
            return buildAck(messageFrame, session, "uid has authed", 0);
        }
        threadPoolExecutor.execute(() -> {
            LonglinkErrorEnum success = LonglinkErrorEnum.SUCCESS;
            try {
                log.info("AuthMessageProcessor  start req={},session={}", JSON.toJSONString(messageFrame), session.toString());
                AuthMessage message = (AuthMessage) messageFrame.getBody();
                if (StringUtils.isBlank(message.sid)) {
                    message.setSid(session.channel().attr(HttpServerHandler.COOKIE_KEY).get());
                }

                session.setDevice(message.getDevice());
                session.setUserId(message.getUid());
                if (StringUtils.isNotEmpty(messageFrame.getHeader().getAppId())) {
                    session.setAppId(messageFrame.getHeader().getAppId());
                    message.setAppId(messageFrame.getHeader().getAppId());
                } else {
                    session.setAppId(message.getAppId());
                }

                message.setIp(session.getRemoteIp());

                    session.setAuth(AuthStatusEnum.AUTHED);
                    UserKey userKey = new UserKey(message.getUid(), message.getAppId(), session.terminal());
                    session.channel().attr(ChannelConst.USER_KEY)
                            .set(userKey);
                    session.setAuthUser(userKey);
                    List<BaseSocketSession> userWebSocketSession = ChannelManager.getInstance().getUserSocketSession(userKey.getUserId(), userKey.getDomain(), ProtocolEnum.WS);
                    if (!CollectionUtils.isEmpty(userWebSocketSession) && userWebSocketSession.size() >= 5) {
                        WebSocketSession preSession = (WebSocketSession) userWebSocketSession.get(0);
                        preSession.closeSocket("You have logged in to more than" + " locations. Please log out and log in again");
                    }
                    ChannelManager.getInstance().bind(session.getChannel());
                    session.channel().attr(ChannelConst.AUTH_STATUS_KEY)
                            .set(AuthStatusEnum.AUTHED.toString());
                    session.channel().attr(ChannelConst.EVENT_ID_LIST)
                            .set(new HashSet<>());
                    session.sendMessage(buildAck(messageFrame, session, "success", 0));
                    session.refresh(true);
            } catch (Exception e) {
                success = LonglinkErrorEnum.ERR_ACCOUNT_TOKEN_INVALID;
                log.error("AuthMessageProcessor exception req={},session={}", JSON.toJSONString(messageFrame), session, e);
            } finally {
            }
        });
        return buildSilenceAck(messageFrame.getHeader());
    }


    public MessageBody doParseBody(int contentType, String msgBody) throws Exception {
        return JacksonObjectMapper.loadObject(msgBody, AuthMessage.class);
    }

    @Override
    public void checkBody(MessageBody body) {
        AuthMessage authMessage = (AuthMessage) body;
        Preconditions.checkArgument(StringUtils.isNotEmpty(authMessage.getUid())
                , "uid field is empty");
        // 设备信息检查
        Preconditions.checkArgument(authMessage.getDevice() != null
                , "device field is null");
        Preconditions.checkArgument(StringUtils.isNotEmpty(authMessage.getDevice().getDeviceId())
                , "deviceId field is empty");
    }

    public Message buildAck(Message messageFrame, BaseSocketSession session, String msg, int code) {
        AuthMessage messageBody = (AuthMessage) messageFrame.getBody();
        Message message = new Message();
        AuthMessageAck authMessageAck = new AuthMessageAck();
        authMessageAck.setMsg(msg);
        authMessageAck.setCode(code);
        authMessageAck.setData(AuthResponse.builder().channelId(session.channel().id().asLongText()).build());
        authMessageAck.setTs(System.currentTimeMillis());
        MessageHeader header = new MessageHeader(MessageTypeEnum.AUTH_ACK.getType());
        header.trackHeader(messageFrame.getHeader());
        message.setHeader(header);
        message.setBody(authMessageAck);
        return message;
    }

    @Data
    public static class AuthMessage implements MessageBody, Serializable {
        private String uid; //用户id  用户属于匿名登录，uid
        private String sid; //auth str  //
        private Device device; //设备信息
        private String appId; //app domain
        private String ip; // 端侧 ip 地址
        private String extra; //json ，其中此处
    }

    @Data
    public static class AuthMessageAck extends RespMessageBody implements Serializable {
        //============ data ===================///
        private String mid; // 端侧产生的msg id
    }
}
